package com.example.android.customviews.charting;

import java.util.ArrayList;
import java.util.List;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.BlurMaskFilter;
import android.graphics.BlurMaskFilter.Blur;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.SweepGradient;
import android.os.Build;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;
import com.example.android.customviews.R;




/**
 * Custom view that shows a pie chart and, optionally, a label.
 */
public class MyPieChart extends ViewGroup {
	

	private List<Item> mData = new ArrayList<Item>();
	
	private float mTotal = 0.0f;
	
	private RectF mPieBounds = new RectF();
	
    private Paint mPiePaint;
    private Paint mTextPaint;
    private Paint mShadowPaint;
    
	private boolean mShowText = false;
	
	private float mTextX = 0.0f;
	private float mTextY = 0.0f;
	private float mTextWidth = 0.0f;
	private float mTextHeight = 0.0f;
	private int mTextPos = TEXTPOS_LEFT;
	
	private float mHighlightStrength = 1.15f;
	
	private PointerView mPointerView;
	private float mPointerRadius = 2.0f;
	private float mPointerX;
	private float mPointerY;

	private int mPieRotation;
	
    private OnCurrentItemChangedListener mCurrentItemChangedListener = null;
	
	private int mTextColor;
	private PieView mPieView;	
	private Scroller mScroller;
	private ValueAnimator mScrollAnimator;
	private GestureDetector mDetector;
	
    // The angle at which we measure the current item. This is
    // where the pointer points.
    private int mCurrentItemAngle;
    
    // the index of the current item.
    private int mCurrentItem = 0;
	private boolean mAutoCenterInSlice;
	private ObjectAnimator mAutoCenterAnimator;

	private RectF mShadowBounds = new RectF();
	
	/**
	 * Draw text to the left of the pie chart
	 */
	public static final int TEXTPOS_LEFT = 0;

	/**
	 * Draw text to the right of the pie chart
	 */
	public static final int TEXTPOS_RIGHT = 1;
	

    /**
     * The initial fling velocity is divided by this amount.
     */
    public static final int FLING_VELOCITY_DOWNSCALE = 4;
    
	/**
	 * 
	 */
	private static final long AUTOCENTER_ANIM_DURATION = 250;

    /**
     * Interface definition for a callback to be invoked when the current
     * item changes.
     */
    public interface OnCurrentItemChangedListener {
        void OnCurrentItemChanged(MyPieChart source, int currentItem);
    }
    
    
    /**
     * Class constructor taking only a context. Use this constructor to create
     * {@link PieChart} objects from your own code.
     *
     * @param context
     */
    public MyPieChart(Context context) {
        super(context);
        init();
    }
    


	public MyPieChart(Context context, AttributeSet attrs) {
		super(context, attrs);
		TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.PieChart, 0, 0);
		try {
            mShowText = a.getBoolean(R.styleable.PieChart_showText, false);
            mTextY = a.getDimension(R.styleable.PieChart_labelY, 0.0f);
            mTextWidth = a.getDimension(R.styleable.PieChart_labelWidth, 0.0f);
            mTextHeight = a.getDimension(R.styleable.PieChart_labelHeight, 0.0f);
            mTextPos = a.getInteger(R.styleable.PieChart_labelPosition, 0);
            mTextColor = a.getColor(R.styleable.PieChart_labelColor, 0xff000000);
            mHighlightStrength = a.getFloat(R.styleable.PieChart_highlightStrength, 1.0f);
            mPieRotation = a.getInt(R.styleable.PieChart_pieRotation, 0);
            mPointerRadius = a.getDimension(R.styleable.PieChart_pointerRadius, 2.0f);
            mAutoCenterInSlice = a.getBoolean(R.styleable.PieChart_autoCenterPointerInSlice, false);
		} finally {
			a.recycle();
		}
		
		init();
	}

	/**
	 * Returns true if the text label should be visible.
	 *
	 * @return True if the text label should be visible, false otherwise.
	 */
	public boolean getShowText() {
		return mShowText;
	}

	/**
	 * Controls whether the text label is visible or not. Setting this property to
	 * false allows the pie chart graphic to take up the entire visible area of
	 * the control.
	 *
	 * @param showText true if the text label should be visible, false otherwise
	 */
	public void setShowText(boolean showText) {
		mShowText = showText;
		invalidate();
	}

	/**
	 * Returns the Y position of the label text, in pixels.
	 *
	 * @return The Y position of the label text, in pixels.
	 */
	public float getTextY() {
		return mTextY;
	}

	/**
	 * Set the Y position of the label text, in pixels.
	 *
	 * @param textY the Y position of the label text, in pixels.
	 */
	public void setTextY(float textY) {
		mTextY = textY;
		invalidate();
	}

	/**
	 * Returns the width reserved for label text, in pixels.
	 *
	 * @return The width reserved for label text, in pixels.
	 */
	public float getTextWidth() {
		return mTextWidth;
	}

	/**
	 * Set the width of the area reserved for label text. This width is constant; it does not
	 * change based on the actual width of the label as the label text changes.
	 *
	 * @param textWidth The width reserved for label text, in pixels.
	 */
	public void setTextWidth(float textWidth) {
		mTextWidth = textWidth;
		invalidate();
	}

	/**
	 * Returns the height of the label font, in pixels.
	 *
	 * @return The height of the label font, in pixels.
	 */
	public float getTextHeight() {
		return mTextHeight;
	}

	/**
	 * Set the height of the label font, in pixels.
	 *
	 * @param textHeight The height of the label font, in pixels.
	 */
	public void setTextHeight(float textHeight) {
		mTextHeight = textHeight;
		invalidate();
	}

	/**
	 * Returns a value that specifies whether the label text is to the right
	 * or the left of the pie chart graphic.
	 *
	 * @return One of TEXTPOS_LEFT or TEXTPOS_RIGHT.
	 */
	public int getTextPos() {
		return mTextPos;
	}

	/**
	 * Set a value that specifies whether the label text is to the right
	 * or the left of the pie chart graphic.
	 *
	 * @param textPos TEXTPOS_LEFT to draw the text to the left of the graphic,
	 *                or TEXTPOS_RIGHT to draw the text to the right of the graphic.
	 */
	public void setTextPos(int textPos) {
		if (textPos != TEXTPOS_LEFT && textPos != TEXTPOS_RIGHT) {
			throw new IllegalArgumentException(
					"TextPos must be one of TEXTPOS_LEFT or TEXTPOS_RIGHT");
		}
		mTextPos = textPos;
		invalidate();
	}

	/**
	 * Returns the strength of the highlighting applied to each pie segment.
	 *
	 * @return The highlight strength.
	 */
	public float getHighlightStrength() {
		return mHighlightStrength;
	}

	/**
	 * Set the strength of the highlighting that is applied to each pie segment.
	 * This number is a floating point number that is multiplied by the base color of
	 * each segment to get the highlight color. A value of exactly one produces no
	 * highlight at all. Values greater than one produce highlights that are lighter
	 * than the base color, while values less than one produce highlights that are darker
	 * than the base color.
	 *
	 * @param highlightStrength The highlight strength.
	 */
	public void setHighlightStrength(float highlightStrength) {
		if (highlightStrength < 0.0f) {
			throw new IllegalArgumentException(
					"highlight strength cannot be negative");
		}
		mHighlightStrength = highlightStrength;
		invalidate();
	}

	/**
	 * Returns the radius of the filled circle that is drawn at the tip of the current-item
	 * pointer.
	 *
	 * @return The radius of the pointer tip, in pixels.
	 */
	public float getPointerRadius() {
		return mPointerRadius;
	}

	/**
	 * Set the radius of the filled circle that is drawn at the tip of the current-item
	 * pointer.
	 *
	 * @param pointerRadius The radius of the pointer tip, in pixels.
	 */
	public void setPointerRadius(float pointerRadius) {
		mPointerRadius = pointerRadius;
		invalidate();
	}

	/**
	 * Returns the current rotation of the pie graphic.
	 *
	 * @return The current pie rotation, in degrees.
	 */
	public int getPieRotation() {
		return mPieRotation;
	}

	/**
	 * Set the current rotation of the pie graphic. Setting this value may change
	 * the current item.
	 *
	 * @param rotation The current pie rotation, in degrees.
	 */
	public void setPieRotation(int rotation) {
		rotation = (rotation % 360 + 360) % 360;
		mPieRotation = rotation;
		mPieView.rotateTo(rotation);

		calcCurrentItem();
	}

	/**
	 * Returns the index of the currently selected data item.
	 *
	 * @return The zero-based index of the currently selected data item.
	 */
	public int getCurrentItem() {
		return mCurrentItem;
	}

	/**
	 * Set the currently selected item. Calling this function will set the current selection
	 * and rotate the pie to bring it into view.
	 *
	 * @param currentItem The zero-based index of the item to select.
	 */
	public void setCurrentItem(int currentItem) {
		setCurrentItem(currentItem, true);
	}

	/**
	 * Set the current item by index. Optionally, scroll the current item into view. This version
	 * is for internal use--the scrollIntoView option is always true for external callers.
	 *
	 * @param currentItem    The index of the current item.
	 * @param scrollIntoView True if the pie should rotate until the current item is centered.
	 *                       False otherwise. If this parameter is false, the pie rotation
	 *                       will not change.
	 */
	private void setCurrentItem(int currentItem, boolean scrollIntoView) {
		mCurrentItem = currentItem;
		if (mCurrentItemChangedListener != null) {
			mCurrentItemChangedListener.OnCurrentItemChanged(this, currentItem);
		}
		if (scrollIntoView) {
			centerOnCurrentItem();
		}
		invalidate();
	}


	/**
	 * Register a callback to be invoked when the currently selected item changes.
	 *
	 * @param listener Can be null.
	 *                 The current item changed listener to attach to this view.
	 */
	public void setOnCurrentItemChangedListener(OnCurrentItemChangedListener listener) {
		mCurrentItemChangedListener = listener;
	}
	
	
    /**
     * Add a new data item to this view. Adding an item adds a slice to the pie whose
     * size is proportional to the item's value. As new items are added, the size of each
     * existing slice is recalculated so that the proportions remain correct.
     *
     * @param label The label text to be shown when this item is selected.
     * @param value The value of this item.
     * @param color The ARGB color of the pie slice associated with this item.
     * @return The index of the newly added item.
     */
    private int addItem(String label, float value, int color) {
    	Item it = new Item();
    	it.mLable = label;
    	it.mColor = color;
    	it.mValue = value;
    	
        // Calculate the highlight color. Saturate at 0xff to make sure that high values
        // don't result in aliasing.
    	it.mHighlight = Color.argb(
    			0xff, 
    			Math.min((int) (mHighlightStrength * (float) Color.red(color)), 0xff), 
    			Math.min((int) (mHighlightStrength * (float) Color.green(color)), 0xff),
    			Math.min((int) (mHighlightStrength * (float) Color.blue(color)), 0xff) );
    	
    	mTotal += value;
    	
    	mData.add(it);
    	
    	onDataChanged();
    	
    	return mData.size() - 1;
	}
    
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // Let the GestureDetector interpret this event
        boolean result = mDetector.onTouchEvent(event);

        // If the GestureDetector doesn't want this event, do some custom processing.
        // This code just tries to detect when the user is done scrolling by looking
        // for ACTION_UP events.
        if ( ! result ) {
        	if ( event.getAction() == MotionEvent.ACTION_UP ) {
                // User is done scrolling, it's now safe to do things like autocenter
        		stopScrolling();
        		result = true;
        	}
        }
        return result;
    }
    
	@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // Do nothing. Do not call the superclass method--that would start a layout pass
        // on this view's children. PieChart lays out its children in onSizeChanged().
    }
	
	@Override
	protected void onDraw(Canvas canvas) {	
		super.onDraw(canvas);
		
		// Draw the shadow
		canvas.drawOval(mShadowBounds, mShadowPaint);
		
		// Draw the label text
		if ( getShowText() ) {
			canvas.drawText(mData.get(mCurrentItem).mLable, mTextX, mTextY, mTextPaint);
		}
		
        // If the API level is less than 11, we can't rely on the view animation system to
        // do the scrolling animation. Need to tick it here and call postInvalidate() until the scrolling is done.
		if ( Build.VERSION.SDK_INT < 11 ) {
			tickScrollAnimation();
			if ( ! mScroller.isFinished() ) {
				postInvalidate();
			}
		}
		
	}
	
    //
    // Measurement functions. This example uses a simple heuristic: it assumes that
    // the pie chart should be at least as wide as its label.
    //
    @Override
    protected int getSuggestedMinimumWidth() {
        return (int) mTextWidth * 2;
    }

    @Override
    protected int getSuggestedMinimumHeight() {
        return (int) mTextWidth;
    }
	
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // Try for a width based on our minimum
		int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth();
		int w = Math.max(minw, MeasureSpec.getSize(widthMeasureSpec));
        
		// Whatever the width ends up being, ask for a height that would let the pie
        // get as big as it can
		int minh = ( w - (int) mTextWidth ) + getPaddingBottom() + getPaddingTop();
		int h = Math.min(MeasureSpec.getSize(heightMeasureSpec), minh);
		
		setMeasuredDimension(w, h);
	} 
	
	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		super.onSizeChanged(w, h, oldw, oldh);
        //
        // Set dimensions for text, pie chart, etc
        //
        // Account for padding
		float xpad = (float) (getPaddingLeft() + getPaddingRight());
		float ypad = (float) (getPaddingBottom() + getPaddingTop());
		
		// Account for the label
		if ( mShowText ) xpad += mTextWidth;
		
		float ww = (float) w - xpad;
		float hh = (float) h - ypad;
		
		//Figure out how big we can make the pie
		float diameter = Math.min(ww, hh);
		mPieBounds = new RectF(
				0.0f,
				0.0f,
				diameter,
				diameter
				);
		
		mPieBounds.offsetTo(getPaddingLeft(), getPaddingTop());
		
		mPointerY = mTextY - (mTextHeight / 2.0f);
		float pointerOffset = mPieBounds.centerY() - mPointerY;
		
		//Make adjustment based on text position
		if ( mTextPos == TEXTPOS_LEFT ) {
			mTextPaint.setTextAlign(Paint.Align.RIGHT);
			if ( mShowText ) mPieBounds.offset(mTextWidth, 0.0f);
			mTextX = mPieBounds.left;
			
			
			if ( pointerOffset < 0) {
				pointerOffset = - pointerOffset;
				mCurrentItemAngle = 255;
			} else {
				mCurrentItemAngle = 135;
			}
			mPointerX = mPieBounds.centerX() - pointerOffset;
		} else {
			//right
			mTextPaint.setTextAlign(Paint.Align.LEFT);
			mTextX = mPieBounds.right;
			
			if ( pointerOffset < 0) {
				pointerOffset  = - pointerOffset;
				mCurrentItemAngle = 315;
			} else {
				mCurrentItemAngle = 45;
			}
			
			mPointerX = mPieBounds.centerX() + pointerOffset;
		}
		
		mShadowBounds = new RectF(
			mPieBounds.left + 10,
			mPieBounds.bottom + 10,
			mPieBounds.right - 10,
			mPieBounds.bottom + 40);
		
		// Layout the child view that actually draws the pie
		mPieView.layout(
				(int) mPieBounds.left, 
				(int) mPieBounds.top, 
				(int) mPieBounds.right, 
				(int) mPieBounds.bottom
					);
		
		mPieView.setPivot( mPieBounds.width() / 2, mPieBounds.height() / 2);
		
		mPointerView.layout(0, 0, w, h);
		
		onDataChanged();
	}
	
    /**
     * Calculate which pie slice is under the pointer, and set the current item
     * field accordingly.
     */
	private void calcCurrentItem() {
		int pointerAngle = (mCurrentItemAngle + 360 + mPieRotation) % 360;
		for(int i = 0 ; i < mData.size() ; ++i) {
			Item it = mData.get(i);
			if ( it.mStartAngle <= pointerAngle && pointerAngle <= it.mEndAngle ) {
				if ( i != mCurrentItem ) {
					setCurrentItem(i, false);
				}
				break;
			}
		}
	}

    /**
     * Do all of the recalculations needed when the data array changes.
     */
	private void onDataChanged() {
        // When the data changes, we have to recalculate
        // all of the angles.
		int currentAngle = 0;
		for(Item it : mData) {
			it.mStartAngle = currentAngle;
			it.mEndAngle = (int) ((float) currentAngle + it.mValue * 360.0f / mTotal);
			currentAngle = it.mEndAngle;
			
			// Recalculate the gradient shaders. There are
			// three values in this gradient, even though only 
			// two are necessary, in order to work around
			// a bug in certain versions of the graphics engine
			// that expects at least 3 values if the
			// position array is non-nulll.
			
			it.mShader = new SweepGradient(
					mPieBounds.width() / 2.0f, 
					mPieBounds.height() / 2.0f, 
					new int[] {
						it.mHighlight,
						it.mHighlight,
						it.mColor,
						it.mColor
					}, 
					new float[] {
						0,
						(float) (360 - it.mEndAngle) / 360.0f,
						(float) (360 - it.mStartAngle) / 360.0f,
						1.0f
					}
					);
		}
		
		calcCurrentItem();
		onScrollFinished();
	}

	private static final int BLUR_RADIUS = 8;
	
	private void init() {
        // Force the background to software rendering because otherwise the Blur
        // filter won't work.
		setLayerToSW(this);
		
		//Set up the paint for the label text
		mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
		mTextPaint.setColor(mTextColor);
		if ( mTextHeight == 0 ) {
			mTextHeight = mTextPaint.getTextSize();
		} else {
			mTextPaint.setTextSize(mTextHeight);
		}
		
		//Set up the paint for the pie slices
		mPiePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
		mPiePaint.setStyle(Paint.Style.FILL);
		mPiePaint.setTextSize(mTextHeight);
		
		//Set up the paint for the shadow
		mShadowPaint = new Paint(0);
		mShadowPaint.setColor(0xff101010);
		mShadowPaint.setMaskFilter(new BlurMaskFilter(BLUR_RADIUS, Blur.NORMAL));
		
		// Add a child view to draw the pie. Putting this in a child view
		// makes it possible to draw it on a separate hardware layer that rotates
		// independently
		mPieView = new PieView(getContext());
		addView(mPieView);
		mPieView.rotateTo(mPieRotation);
		
        // The pointer doesn't need hardware acceleration, but in order to show up
        // in front of the pie it also needs to be on a separate view.
		mPointerView = new PointerView(getContext());
		addView(mPointerView);
		
		// Set up an animator to animate the PieRotation property. This is used to
		// correct the pie's orientation after the user lets go of it.
		if ( Build.VERSION.SDK_INT >= 11) {
			mAutoCenterAnimator = ObjectAnimator.ofInt(MyPieChart.this, "MyPieRotation", 0);
			
			mAutoCenterAnimator.addListener(new AnimatorListener() {
				
				@Override
				public void onAnimationStart(Animator animation) {
				}
				
				@Override
				public void onAnimationRepeat(Animator animation) {
				}
				
				@Override
				public void onAnimationEnd(Animator animation) {
					mPieView.decelerate();
				}
				
				@Override
				public void onAnimationCancel(Animator animation) {
				}
			});
		}
		
		//Create a Scroller to handle the fling gesture
		if ( Build.VERSION.SDK_INT < 11) {
			mScroller = new Scroller(getContext());
		} else {
			mScroller = new Scroller(getContext(), null, true);
		}
        // The scroller doesn't have any built-in animation functions--it just supplies
        // values when we ask it to. So we have to have a way to call it every frame
        // until the fling ends. This code (ab)uses a ValueAnimator object to generate
        // a callback on every animation frame. We don't use the animated value at all.
		if ( Build.VERSION.SDK_INT >= 11) {
			mScrollAnimator = ValueAnimator.ofFloat(0,1);
			mScrollAnimator.addUpdateListener(new AnimatorUpdateListener() {
				@Override
				public void onAnimationUpdate(ValueAnimator animation) {
					tickScrollAnimation();
				}
			});
		}
		
        // Create a gesture detector to handle onTouch messages
		mDetector = new GestureDetector(getContext(), new GestureListener());
		
        // Turn off long press--this control doesn't use it, and if long press is enabled,
        // you can't scroll for a bit, pause, then scroll some more (the pause is interpreted
        // as a long press, apparently)
        mDetector.setIsLongpressEnabled(false);

        if ( this.isInEditMode() ) {
            Resources res = getResources();
            addItem("Annabelle", 3, res.getColor(R.color.bluegrass));
            addItem("Brunhilde", 4, res.getColor(R.color.chartreuse));
            addItem("Carolina", 2, res.getColor(R.color.emerald));
            addItem("Dahlia", 3, res.getColor(R.color.seafoam));
            addItem("Ekaterina", 1, res.getColor(R.color.slate));
        }
	}
	
	private void tickScrollAnimation() {
        if (!mScroller.isFinished()) {
            mScroller.computeScrollOffset();
            setPieRotation(mScroller.getCurrY());
        } else {
            if (Build.VERSION.SDK_INT >= 11) {
                mScrollAnimator.cancel();
            }
            onScrollFinished();
        }
    }
    /**
     * Called when the user finishes a scroll action.
     */
    private void onScrollFinished() {
        if (mAutoCenterInSlice) {
            centerOnCurrentItem();
        } else {
            mPieView.decelerate();
        }
    }

    private void setLayerToSW(View v) {
        if (!v.isInEditMode() && Build.VERSION.SDK_INT >= 11) {
            setLayerType(View.LAYER_TYPE_SOFTWARE, null);
        }
    }
    
    private void setLayerToHW(View v) {
        if (!v.isInEditMode() && Build.VERSION.SDK_INT >= 11) {
            setLayerType(View.LAYER_TYPE_HARDWARE, null);
        }
    }

	public void stopScrolling() {
		mScroller.forceFinished(true);
		if ( Build.VERSION.SDK_INT >= 11) {
			mAutoCenterAnimator.cancel();
		}
		
		onScrollFinished();
	}
    
    /**
     * Kicks off an animation that will result in the pointer being centered in the
     * pie slice of the currently selected item.
     */
    private void centerOnCurrentItem() {
    	Item current = mData.get(getCurrentItem());
    	int targetAngle = current.mStartAngle + ( current.mEndAngle - current.mStartAngle) / 2;
    	targetAngle -= mCurrentItemAngle;
    	if ( targetAngle < 90 && mPieRotation > 180) targetAngle += 360;
    	
    	if ( Build.VERSION.SDK_INT >= 11) {
            // Fancy animated version
            mAutoCenterAnimator.setIntValues(targetAngle);
            mAutoCenterAnimator.setDuration(AUTOCENTER_ANIM_DURATION).start();
    	} else {
            // Dull non-animated version
            //mPieView.rotateTo(targetAngle);
    	}
    }

    
    private class PieView extends View {
    	//Used for SDK < 11
    	private float mRotation = 0;
    	private Matrix mTransform = new Matrix();
    	private PointF mPivot = new PointF();
    	
		public PieView(Context context) {
			super(context);
		}
		
        /**
         * Enable hardware acceleration (consumes memory)
         */
        public void accelerate() {
            setLayerToHW(this);
        }
        
        /**
         * Disable hardware acceleration (releases memory)
         */
        public void decelerate() {
            setLayerToSW(this);
        }
        
        
        @Override
        protected void onDraw(Canvas canvas) {
        	super.onDraw(canvas);
        	
        	if ( Build.VERSION.SDK_INT < 11) {
        		mTransform.set(canvas.getMatrix());
        		mTransform.preRotate(mRotation, mPivot.x , mPivot.y );
        		canvas.setMatrix(mTransform);
        	}
        	
        	for(Item it : mData) {
        		mPiePaint.setShader(it.mShader);
        		canvas.drawArc(mBounds, 
        				360 - it.mEndAngle, 
        				it.mEndAngle - it.mStartAngle,
        				true, mPiePaint);
        	}
        }
        
        
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        	mBounds = new RectF(0,0,w,h);
        };
        
        RectF mBounds;
        
        public void rotateTo(float pieRotation) {
        	mRotation = pieRotation;
        	if ( Build.VERSION.SDK_INT >= 11) {
        		setRotation(pieRotation);
        	} else {
        		invalidate();
        	}
        }

        public void setPivot(float x, float y) {
        	mPivot.x = x;
        	mPivot.y = y;
        	
        	if ( Build.VERSION.SDK_INT >= 11) {
        		setPivotX(x);
        		setPivotY(y);
        	} else {
        		invalidate();
        	}
        }
    }
    
    /**
     * View that draws the pointer on top of the pie chart
     */
    private class PointerView extends View {

        /**
         * Construct a PointerView object
         *
         * @param context
         */
        public PointerView(Context context) {
            super(context);
        }

        @Override
        protected void onDraw(Canvas canvas) {
            canvas.drawLine(mTextX, mPointerY, mPointerX, mPointerY, mTextPaint);
            canvas.drawCircle(mPointerX, mPointerY, mPointerRadius, mTextPaint);
        }
    }
    
	private class Item {
		public String mLable;
		public float mValue;
		public int mColor;
		
		// computed values
		public int mStartAngle;
		public int mEndAngle;
		
		public int mHighlight;
		public Shader mShader;
	} 

	public class GestureListener extends GestureDetector.SimpleOnGestureListener {
		@Override
		public boolean onScroll(MotionEvent e1, MotionEvent e2,
				float distanceX, float distanceY) {
			float scrollTheta = vectorToScalarScroll(
					distanceX,
					distanceY,
					e2.getX() - mPieBounds.centerX(),
					e2.getY() - mPieBounds.centerY());
			setPieRotation(getPieRotation() - (int) scrollTheta / FLING_VELOCITY_DOWNSCALE);
			return true;
		}
		
		@Override
		public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
				float velocityY) {
			// Set up the scroller for a fling
			float scrollTheta = vectorToScalarScroll(
					velocityX, 
					velocityY, 
					e2.getX() - mPieBounds.centerX(),
					e2.getY() - mPieBounds.centerY());
			
			mScroller.fling(
					0,
					(int) getPieRotation(),
					0,
					(int) scrollTheta / FLING_VELOCITY_DOWNSCALE,
					0,
					0,
					Integer.MIN_VALUE,
					Integer.MAX_VALUE
					);
			
			// Start the animator and tell it to animate for the expected duration of the fling
			if ( Build.VERSION.SDK_INT >= 11) {
				mScrollAnimator.setDuration(mScroller.getDuration());
				mScrollAnimator.start();
			}
			return true;
		}
		
		@Override
		public boolean onDown(MotionEvent e) {
            // The user is interacting with the pie, so we want to turn on acceleration
            // so that the interaction is smooth.
			mPieView.accelerate();
			if ( isAnimationRunning() ) {
				stopScrolling();
			}
			return true;
		}
	}


	public float vectorToScalarScroll(float distanceX, float distanceY, float x, float y) {
		//get the length of vector
		float l = (float) Math.sqrt(distanceX * distanceX + distanceY * distanceY);
		
        // decide if the scalar should be negative or positive by finding
        // the dot product of the vector perpendicular to (x,y). 
		float crossX = - y;
		float crossY = x;
		
		float dot = (crossX * distanceX + crossY * distanceY);
		float sign = Math.signum(dot);
		
		return l * sign;
	}


	public boolean isAnimationRunning() {
		return ! mScroller.isFinished() || (Build.VERSION.SDK_INT >= 11 && mAutoCenterAnimator.isRunning());
	}


}
